#ifndef _RING_QUEUE_HPP_
#define _RING_QUEUE_HPP_

#include <iostream>
#include <vector>
#include <pthread.h>
#include "sem.hpp"

const int g_defualt_num = 5;                            // 设置环形队列的默认大小

/* 环形队列 */
template<class T>
class RingQueue{
public:
    RingQueue(int defualt_num = g_defualt_num)
        :_ring_queue(defualt_num), _num(defualt_num)
        , _c_point(0), _p_point(0)                      // 指向初始为 0
        ,_space_Sem(defualt_num), _data_Sem(0)          // 空间资源：起始为 N；数据资源：起始为 0！
    {
        // 初始化锁
        pthread_mutex_init( &_plock, nullptr);
        pthread_mutex_init( &_clock, nullptr);
    }
    ~RingQueue(){
        // 销毁锁
        pthread_mutex_destroy( &_plock);
        pthread_mutex_destroy( &_clock);
    }
 
    // 生产者：关心：空间资源！
    void push(const T &in){
        // int idx = 0;                                 // 写法错误：idx 是临时变量，每次调用都会重新初始化！
#if 0
        static int idx = 0;                             // 可行
        _ring_queue[idx++] = in;
        idx %= _num;                                    //  模拟环形队列
#endif

#if 0
        _ring_queue[_p_point++] = in;
        _p_point %= _num;                               //  模拟环形队列
        /**
         * 注意目前为止：由于缺少一个“安全限制”；
         * 对于持续生产而言，如果生产量对于了队列容量，可能导致数据覆盖；
         * 解决方式：引入信号量进行约束！
        */
#endif
        /**
         * 注意：实际，多生产者是可以同时生产的！多个空间的预定也是可以同时进行的！
         * 限制的只是：同一个空间，只允许一个生产者去占用！
         * 
         * 信号量：一定是安全的！一定是原子性的计数器！
         * 
         * 提问：加锁与空间预定（信号量申请）的先后顺序是？
         * 答：先申请信号量！
         * 提问：为什么？
         * 答：效率的高低取决于资源分配的速度！（信号量就是资源是否可用的一种度量方式）
         * 实际示例：如演唱会门票的销售问题，如果是开场前先排队，再一个一个售票，势必导致效率低下，且拥堵，
         * 此时个体入场的时间成本是：排队时间+售票时间（选座等）
         * 如果采用提前预定是售票，即在开场排队前先分配了位置，
         * 此时个体入场的时间成本是：排队时间
        */

        // 1. 预定空间
        _space_Sem.p();

        // 多线程版：加锁
        pthread_mutex_lock(&_plock);        // 可能失败！
        // 多线程版：走到此处：就是竞争成功的生产者（唯一性）【锁间代码共享】

        // // 1. 预定空间
        // _space_Sem.p();
        // 2. 数据生产【数据占用期间，生产者本身不会去释放掉空间！】
        _ring_queue[_p_point++] = in;
        _p_point %= _num;                               //  模拟环形队列

        // 多线程版：解锁
        pthread_mutex_unlock(&_plock);

        // 3. 数据量增加
        _data_Sem.v();


    }

    // 消费者：关心：数据资源！
    void pop(T *out){
        // 1. 申请消费：消费的前提是有数据
        _data_Sem.p();

        // 多线程版：加锁
        pthread_mutex_lock(&_clock);
        // 多线程版：走到此处：就是竞争成功的消费者（唯一性）【锁间代码共享】

        // // 1. 申请消费：消费的前提是有数据
        // _data_Sem.p();
        // 2. 拿去数据，进行消费
        *out = _ring_queue[_c_point++];
        _c_point %= _num;                               //  模拟环形队列

        // 多线程版：解锁
        pthread_mutex_unlock(&_clock);

        // 3. 数据被取走，空间要释放！
        _space_Sem.v();


    }

    /**
     * 提问：是否会出现 _p_point == _c_point 的情况？
     * 答：不会，使用了信号量，严格保证了数据的生产一定先于消费【有数据才消费】，保证了消费的前提是有数据！
    */


    // 消息输出
    void debug(){
        std::cerr << "size：" << _ring_queue.size() << " ；num：" << _num << std::endl;
    }
private:
    std::vector<T> _ring_queue;
    int _num;
    int _c_point;   // 消费者下标，指向一定有数据位置
    int _p_point;   // 生产者下标，指向一定可以存放数据位置
    Sem _space_Sem; // 生产者信号量
    Sem _data_Sem;  // 消费者信号量
    pthread_mutex_t _plock;     // 生产者锁
    pthread_mutex_t _clock;     // 消费者锁
};

#endif